﻿using ModengTerm.Base;
using ModengTerm.Base.DataModels;
using ModengTerm.Base.Enumerations;
using ModengTerm.Terminal.Session;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Markup;

namespace ModengTerm.Terminal.Watch
{
    public class UnixAdbWatcher : UnixWatcher
    {
        public class FileNames
        {
            /// <summary>
            /// 保存到设备里的临时文件名
            /// </summary>
            public string TempFileName { get; set; }

            /// <summary>
            /// 保存到设备里的临时文件路径
            /// </summary>
            public string TempFilePath { get; set; }

            /// <summary>
            /// 本地保存从设备上pull下来的文件路径
            /// </summary>
            public string LocalFilePath { get; set; }
        }

        #region 类变量

        private static log4net.ILog logger = log4net.LogManager.GetLogger("AdbUnixWatcher");

        #endregion

        #region 实例变量

        private string shellPrompt;
        private string adbPath;
        private int watchTimeout;
        private Process readProcess; // 用来读取文件的进程
        private Stream readStream;
        private Stream writeStream;
        private Encoding writeEncoding;
        private Encoding readEncoding;
        private string tempDir;
        private Dictionary<string, FileNames> cmd2FileName;
        private List<ScriptItem> userNaemPasswords;

        #endregion

        #region 构造方法

        public UnixAdbWatcher(XTermSession session, SessionDriver driver) :
            base(session, driver)
        {
            this.cmd2FileName = new Dictionary<string, FileNames>();

            this.shellPrompt = session.GetOption<string>(OptionKeyEnum.WATCH_ADB_PROMPT, OptionDefaultValues.WATCH_ADB_PROMPT);
            this.adbPath = session.GetOption<string>(OptionKeyEnum.WATCH_ADB_PATH, OptionDefaultValues.WATCH_ADB_PATH);
            this.userNaemPasswords = session.GetOption<List<ScriptItem>>(OptionKeyEnum.WATCH_ADB_PASSWORDS, OptionDefaultValues.WATCH_ADB_PASSWORDS);
            this.writeEncoding = Encoding.GetEncoding(session.GetOption<string>(OptionKeyEnum.TERM_WRITE_ENCODING, VTBaseConsts.DefaultWriteEncoding));
            this.readEncoding = Encoding.GetEncoding(session.GetOption<string>(OptionKeyEnum.TERM_READ_ENCODING, VTBaseConsts.DefaultReadEncoding));
            this.watchTimeout = session.GetOption<int>(OptionKeyEnum.WATCH_ADB_LOGIN_TIMEOUT, OptionDefaultValues.WATCH_ADB_LOGIN_TIMEOUT);
            this.tempDir = session.GetOption<string>(OptionKeyEnum.WATCH_ADB_TEMP_DIR, OptionDefaultValues.WATCH_ADB_TEMP_DIR);
            this.tempDir = this.tempDir.TrimEnd('/');
        }

        #endregion

        #region 实例方法

        private bool UserLogin()
        {
            foreach (ScriptItem userNamePassword in this.userNaemPasswords)
            {
                string terminator = string.Empty;

                switch ((LineTerminators)userNamePassword.Terminator)
                {
                    case LineTerminators.None: break;
                    case LineTerminators.LF: terminator = "\n"; break;
                    case LineTerminators.CR: terminator = "\r"; break;
                    case LineTerminators.CRLF: terminator = "\r\n"; break;
                    default: throw new NotImplementedException();
                }

                string send = string.Format("{0}{1}", userNamePassword.Send, terminator);

                if (!this.HandlePrompt(userNamePassword.Expect, send, readStream, writeStream, this.watchTimeout)) 
                {
                    logger.ErrorFormat("adb启动失败, 登录失败, {0}", userNamePassword.Expect);
                    return false;
                }
            }

            // 下次读取到shPrompt的时候就说明登录成功
            if (!ReadUntil(readStream, this.shellPrompt, this.watchTimeout))
            {
                logger.ErrorFormat("adb启动失败, 读取提示符失败, {0}", this.shellPrompt);
                return false;
            }

            return true;
        }

        private bool ReadUntil(Stream streamReader, string until, int timeout)
        {
            string read = string.Empty;
            int left = timeout;

            while (true)
            {
                byte[] bytes = new byte[1024];
                int size = streamReader.Read(bytes, 0, bytes.Length);
                if (size < 0)
                {
                    // 读取失败直接退出
                    return false;
                }
                else if (size == 0)
                {
                    // 没读取到数据
                }
                else
                {
                    string read1 = Encoding.ASCII.GetString(bytes, 0, size);
                    read += read1;

                    if (read.Contains(until))
                    {
                        return true;
                    }
                }

                // 50毫秒之后再继续读
                Thread.Sleep(50);

                left -= 50;
                if (left <= 0)
                {
                    return false;
                }
            }
        }

        private bool HandlePrompt(string prompt, string write, Stream streamReader, Stream streamWriter, int timeout)
        {
            if (!ReadUntil(streamReader, prompt, timeout))
            {
                return false;
            }

            byte[] writeBytes = Encoding.ASCII.GetBytes(write);

            streamWriter.Write(writeBytes, 0, writeBytes.Length);
            streamWriter.Flush();

            return true;
        }

        private bool OpenProcess()
        {
            if (this.readProcess != null && !this.readProcess.HasExited)
            {
                return true;
            }

            this.CloseProcess();

            if (!VTermUtils.StartAdbServer(this.session))
            {
                return false;
            }

            string exePath = this.session.GetOption<string>(OptionKeyEnum.WATCH_ADB_PATH, OptionDefaultValues.WATCH_ADB_PATH);

            ProcessStartInfo startInfo = new ProcessStartInfo()
            {
                FileName = exePath,
                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                UseShellExecute = false,
                WindowStyle = ProcessWindowStyle.Hidden,
                CreateNoWindow = true,
                Arguments = "shell"
            };

            try
            {
                this.readProcess = Process.Start(startInfo);
            }
            catch (Exception ex)
            {
                logger.Error("启动readProcess异常", ex);
                return false;
            }

            this.readStream = this.readProcess.StandardOutput.BaseStream;
            this.writeStream = this.readProcess.StandardInput.BaseStream;

            if (!this.UserLogin())
            {
                this.CloseProcess();
                return false;
            }

            return true;
        }

        private void CloseProcess()
        {
            if (this.readProcess == null)
            {
                return;
            }

            try
            {
                if (!this.readProcess.HasExited)
                {
                    this.readProcess.Kill();
                }
                this.readProcess.WaitForExit();
                this.readProcess.Dispose();
            }
            catch (Exception ex)
            {
                logger.Error("关闭readProcess异常", ex);
            }
            finally
            { }

            this.readProcess = null;
        }

        private string CommonExecute(string command)
        {
            if (!this.OpenProcess())
            {
                return string.Empty;
            }

            // 根据要读取的文件生成一个保存在临时目录里的临时文件名
            FileNames fileNames;
            if (!this.cmd2FileName.TryGetValue(command, out fileNames))
            {
                string tempFileName = string.Format("modengterm_watch_{0}", command);
                tempFileName = VTBaseUtils.TrimInvalidFileNameChars(tempFileName);

                fileNames = new FileNames()
                {
                    TempFileName = tempFileName,
                    TempFilePath = string.Format("{0}/{1}", this.tempDir, tempFileName),
                    LocalFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, tempFileName)
                };
                this.cmd2FileName[command] = fileNames;
            }

            #region 执行读取指令;

            string write = string.Format("{0} > {1}\r\n", command, fileNames.TempFilePath);
            byte[] writeBytes = this.writeEncoding.GetBytes(write);

            try
            {
                this.writeStream.Write(writeBytes);
                this.writeStream.Flush();
            }
            catch (Exception ex)
            {
                logger.Error("readFile异常", ex);
                return string.Empty;
            }

            // 下次读到提示符就表示指令执行结束
            if (!this.ReadUntil(this.readStream, this.shellPrompt, this.watchTimeout))
            {
                return string.Empty;
            }

            #endregion

            #region 执行AdbPull指令拷贝到本地

            string content;
            string message;
            AdbReadResult readResult = VTermUtils.AdbReadFile(this.adbPath, fileNames.TempFilePath, fileNames.LocalFilePath, out content, out message);
            if (readResult != AdbReadResult.Susccess)
            {
                logger.ErrorFormat("{0}, {1}", readResult, message);
                return string.Empty;
            }

            #endregion

            return content;
        }

        #endregion

        #region UnixWatcher

        public override void Initialize()
        {
            base.Initialize();
        }

        public override void Release()
        {
            base.Release();
        }

        protected override string ReadFile(string filePath)
        {
            string command = string.Format("cat {0}", filePath);

            return this.CommonExecute(command);
        }

        protected override string Execute(string command)
        {
            return this.CommonExecute(command);
        }

        #endregion
    }
}
